﻿#region Copyright (c) 2008 plexBB Team

/*  Copyright (c) 2008 plexBB Team
 *  
 *  This file is free software: you may copy, redistribute and/or modify it  
 *  under the terms of the GNU General Public License as published by the  
 *  Free Software Foundation, either version 2 of the License, or (at your  
 *  option) any later version.  
 *  
 *  This file is distributed in the hope that it will be useful, but  
 *  WITHOUT ANY WARRANTY; without even the implied warranty of  
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU  
 *  General Public License for more details.  
 *  
 *  You should have received a copy of the GNU General Public License  
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 */

#endregion

using System;
using System.Collections;
using System.Data.SqlClient;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;
using PlexBB.Data;
using PlexBB.ModuleModel;

namespace PlexBB.Install
{
    public class PlexBBInstaller
    {
        private readonly PlexBBConfigurationSection _configuration;

        public PlexBBInstaller(PlexBBConfigurationSection configuration)
        {
            _configuration = configuration;
        }

        /// <summary>
        /// Installs plexBB
        /// 1. [DEBUG only] Creates DB, if DB doesn't exists.
        /// 2. Throws exception if DB doesn't exist.
        /// 3. If DB doesn't contain plexbb_GlobalSetting table, then creates plexBB tables (by executing Schema.sql)
        /// 4. Adds modules records to Modules table (if it is empty).
        /// 5. Sets specified setting (admin username, email, etc.) From initialSetupSetting section of web.config
        /// </summary>
        public void InstallIfRequired()
        {
            // conditional: DEBUG
            CreateDatabaseIfRequired();

            // check if plexbb_GlobalSetting table exists
            // execute Schema.sql if it doesn't
            using (SqlConnection connection = new SqlConnection(_configuration.ConnectionString))
            {
                connection.Open();
                bool tableExists =
                    connection.GetSchema("Tables", new[] { null, null, _configuration.DatabaseTablePrefix + "ForumSession", null }).Rows.Count > 0;
                if (!tableExists)
                {
                    using (Stream schemaStream = Util.GetResourceStream("Install/Schema.sql"))
                    using (StreamReader schemaReader = new StreamReader(schemaStream))
                    {
                        string schemaSql = schemaReader.ReadToEnd();
                        schemaSql = Regex.Replace(schemaSql, "(?<![a-zA-Z])plexbb_", _configuration.DatabaseTablePrefix);
                        string[] queries = Regex.Split(schemaSql, @"^\s*GO\s*$", RegexOptions.Multiline);
                        SqlCommand command = new SqlCommand
                                                 {
                                                     Connection = connection
                                                 };
                        foreach (string query in queries)
                        {
                            if (query.Trim().Length > 0)
                            {
                                command.CommandText = query;
                                command.ExecuteNonQuery();
                            }
                        }
                    }
                }
            }

            // add modules from Modules.xml if Modules table is empty
            using (PlexBBData data = new PlexBBData(_configuration.ConnectionString, _configuration.DatabaseTablePrefix))
            {
                if (data.Modules.Count() == 0)
                {
                    SetupModules(data);
                }
            }
        }

        [Conditional("DEBUG")]
        private void CreateDatabaseIfRequired()
        {
            try
            {
                using (SqlConnection connection = new SqlConnection(_configuration.ConnectionString))
                {
                    connection.Open();
                }
            }
            catch (SqlException)
            {
                // create DB
                SqlConnectionStringBuilder csb = new SqlConnectionStringBuilder(_configuration.ConnectionString);
                string initialCatalog = csb.InitialCatalog;
                csb.InitialCatalog = string.Empty;
                using (SqlConnection connection = new SqlConnection(csb.ConnectionString))
                {
                    connection.Open();
                    SqlCommand createDbCommand = new SqlCommand("CREATE DATABASE " + initialCatalog, connection);
                    createDbCommand.ExecuteNonQuery();
                    // need to wait before DB is active
                    Thread.Sleep(5000);
                }
            }
        }

        private static void SetupModules(PlexBBData data)
        {
            // add defaut modules
            using (Stream modulesStream = Util.GetResourceStream("Install/Modules.xml"))
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(modulesStream);

                foreach (XmlElement element in doc.DocumentElement.ChildNodes)
                {
                    ModuleType type;
                    switch (element.Name)
                    {
                        case "ACP":
                            type = ModuleType.Acp;
                            break;
                        case "MCP":
                            type = ModuleType.Mcp;
                            break;
                        case "UCP":
                            type = ModuleType.Ucp;
                            break;
                        default:
                            throw new PlexBBException("Invalid module type: " + element.Name);
                    }

                    // use stack to be able to loop into multiple levels without 
                    // recursive calls (just for fun?)
                    Stack stack = new Stack();
                    stack.Push(new object[] { element.ChildNodes.GetEnumerator(), null, 10 });
                    while (stack.Count > 0)
                    {
                        object[] stackItem = (object[])stack.Peek();
                        IEnumerator enumerator = (IEnumerator)stackItem[0];
                        Module parentCategory = (Module)stackItem[1];
                        int positionNumber = (int)stackItem[2];

                        while (enumerator.MoveNext())
                        {
                            XmlElement curElement = (XmlElement)enumerator.Current;
                            Module module = new Module
                                                {
                                                    ModuleType = type,
                                                    Position = positionNumber,
                                                    ParentModule = parentCategory
                                                };
                            positionNumber++;
                            stackItem[2] = positionNumber;

                            data.Modules.InsertOnSubmit(module);

                            if (curElement.Name == "Module")
                            {
                                // add module
                                string controlType = curElement.GetAttribute("Type");
                                string modeName = curElement.GetAttribute("Mode");
                                bool display = curElement.HasAttribute("Display") ? Convert.ToBoolean(curElement.GetAttribute("Display")) : true;

                                IModuleInfo moduleInfo = (IModuleInfo)Activator.CreateInstance(Type.GetType(controlType + "Info"));
                                try
                                {
                                    ModuleMode moduleMode = moduleInfo.SupportedModes.Single(m => m.ModeName == modeName);
                                    module.ControlType = controlType;
                                    module.LangName = moduleMode.LangName;
                                    module.ModuleMode = moduleMode.ModeName;
                                    module.Display = display;
                                }
                                catch (InvalidOperationException)
                                {
                                    throw new PlexBBException(string.Format("Mode {0} not found for module {1}.", modeName, controlType));
                                }
                            }
                            else
                            {
                                // add category
                                module.LangName = curElement.Name;

                                enumerator = curElement.ChildNodes.GetEnumerator();
                                parentCategory = module;
                                positionNumber = 10;
                                stackItem = new object[] { enumerator, parentCategory, positionNumber };
                                stack.Push(stackItem);
                            }
                        }
                        stack.Pop();
                    }
                }
            }

            data.SubmitChanges();
        }
    }
}